package com.kingwang.training.designpattern.exam.bo.event;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;

import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 事件总线
 *
 * @author King
 */
@Configuration
@EnableAsync
public class EventBus {
    private final Map<Class<? extends Event>, Set<EventHandler<? extends Event>>> eventHandlers =
            new ConcurrentHashMap<>();

    /**
     * 注册事件处理器
     *
     * @param eventClass
     * @param eventHandler
     * @param <T>
     */
    public <T extends Event> void register(Class<T> eventClass,
                                           EventHandler<T> eventHandler) {
        if (!this.eventHandlers.containsKey(eventClass)) {
            this.eventHandlers.put(eventClass, new HashSet<>());
        }

        Set<EventHandler<? extends Event>> handlers = this.eventHandlers.get(eventClass);
        handlers.add(eventHandler);
    }

    /**
     * 发布事件
     *
     * @param event
     * @param <T>
     */
    public <T extends Event> void publish(T event) {
        Set<EventHandler<? extends Event>> handlers = getEventHandlers(event);

        for (EventHandler<? extends Event> eventHandler : handlers) {
            if (eventHandler.isAsync()) {
                doPublishAsync((EventHandler<T>) eventHandler, event);
            } else {
                ((EventHandler<T>) eventHandler).handle(event);
            }
        }
    }

    private <T extends Event> Set<EventHandler<? extends Event>> getEventHandlers(T event) {
        Class<? extends Event> eventClass = event.getClass();

        if (this.eventHandlers.containsKey(eventClass)) {
            return this.eventHandlers.get(eventClass);
        }

        return Collections.emptySet();
    }

    @Async
    <T extends Event> void doPublishAsync(EventHandler<T> eventHandler, T event) {
        eventHandler.handle(event);
    }
}
